package halftone.errdiff.pipeline

import chisel3._
import chisel3.util.{Counter, Decoupled, MuxLookup}
import halftone.ErrDiffConfig
import halftone.errdiff.ErrDiffCorePipParam._
import tools.bus.BramNativePortFull

class ErrorOutPip(config: ErrDiffConfig) extends Module {
  val io = IO(new Bundle {
    val in  = Flipped(Decoupled(new ThreshCalc2ErrorOutPip(config.posWidth, config.errorWidth)))
    val pa  = Flipped(new BramNativePortFull(config.bramDataBits, config.bramAddrBits))
    val err = Output(SInt(config.errorWidth.W))
    val out = Decoupled(new ErrorOut2WriteBinaryPip(config.posWidth))
    val err_fc = Output(SInt(config.errorWidth.W))
    val stop = Input(Bool())
  })

  // Registers(for value storage and state presentation)
  val pos         = Reg(UInt(config.posWidth.W))
  val bval        = Reg(Bool())
  val errOut      = Reg(Vec(4, SInt(config.errorWidth.W)))
  val busy        = RegInit(false.B)
  val resultValid = RegInit(false.B)

  io.out.valid := resultValid
  io.in.ready  := true.B
  // useless signals
  io.pa     := DontCare
  io.pa.en  := false.B
  io.pa.we  := false.B
  io.pa.din := 0.U

  // variables
  val diffBelowRight = pos + (config.imageCol + 1).U
  val diffBelow      = pos + config.imageCol.U
  val diffBelowLeft  = pos + (config.imageCol - 1).U
  val (cnt, cntWrap) = Counter(busy && !resultValid, CycleNr)

  // Emit outputs
  io.out.bits.pos  := pos
  io.out.bits.bval := bval

  /** Forwarding to ThreshCalc stage */
  io.err := errOut(0)
  /** Store first column error */
  val err_fc_r = RegInit(0.S(config.errorWidth.W))
  when(pos % config.imageCol.U === 1.U) { err_fc_r := errOut(3) }
  io.err_fc := err_fc_r

  when(!resultValid) {
    io.pa.en := MuxLookup(cnt, false.B)(
      Seq(
        0.U -> !(config.isLastColumn(pos) || config.isLastRow(pos)), // below right
        1.U -> !config.isLastRow(pos), // below
        2.U -> !(config.isFirstColumn(pos) || config.isLastRow(pos)) // below left
      )
    )
    io.pa.we := io.pa.en
    io.pa.addr := MuxLookup(cnt, 0.U)(
      Seq(
        0.U -> diffBelowRight,
        1.U -> diffBelow,
        2.U -> diffBelowLeft
      )
    )

    /* write errors to cache */
    io.pa.din := errOut(cnt + 1.U).asUInt
  }

  when(cntWrap && !io.stop) { resultValid := true.B }
  when(io.out.fire) {
    resultValid := false.B
  }
  when(io.in.fire) {
    val inBundle = io.in.deq()
    pos    := inBundle.pos
    bval   := inBundle.bval
    errOut := inBundle.errOut
    busy   := true.B
  }
}
